From DNS to
OkHttp

A complete deep-dive into how client–server connections are established, secured, attacked — and how Android's OkHttp masters them all.

DNS Resolution TCP 3-Way Handshake TLS 1.3 Certificate Pinning Connection Pooling MITM Attacks HTTP/2 Multiplexing
connection lifecycle — end to end
App
Request
User intent
DNS
Lookup
IP resolution
TCP
Handshake
SYN · SYNACK · ACK
TLS
Handshake
Cipher negotiation
HTTP
Request
Encrypted transfer
Response
+ Pool
Reuse socket
Step 01

DNS Resolution

Before any byte is sent, your device must translate a human-readable hostname into an IP address. This chain of delegated authority is called the Domain Name System.

DNS resolution chain
Browser
/ App
cache check first
OS
Cache
/etc/hosts + local cache
Recursive
Resolver
ISP / 8.8.8.8
Root
Servers
13 clusters
TLD
Server
.com / .io / .net
Auth.
Server
returns A/AAAA record
📦
Record Types
A — IPv4 address (93.184.216.34)
AAAA — IPv6 address
CNAME — alias to another hostname
MX — mail exchange
TXT — arbitrary text (SPF, DKIM)
SRV — service discovery
⏱️
TTL & Caching
Each DNS record carries a TTL (Time To Live) in seconds. Once cached, your resolver won't re-query until TTL expires. Short TTLs (30–60s) enable fast failover. Long TTLs (86400s) reduce latency for stable IPs.
🔐
DNS Security
DNSSEC — cryptographic signatures on records
DoH — DNS over HTTPS (port 443, encrypted)
DoT — DNS over TLS (port 853)
Prevents spoofing and eavesdropping on queries
Step 02

TCP Three-Way Handshake

TCP is a connection-oriented protocol. Before data flows, client and server must synchronize sequence numbers through a precise ritual — the 3-way handshake.

TCP connection establishment
CLIENT
SYN
seq=x, SYN=1
SYN-ACK
seq=y, ack=x+1
ACK
ack=y+1
✓ ESTABLISHED
SERVER
01
SYN — Synchronize Client → Server

The client sends a TCP segment with the SYN flag set. It includes a randomly chosen Initial Sequence Number (ISN) — e.g., seq=1000. This number ensures packets are reassembled in order and prevents replay attacks. The server is now in SYN_RECEIVED state.

02
SYN-ACK — Synchronize Acknowledge Server → Client

The server responds with both SYN and ACK flags set. It acknowledges the client's ISN (ack = x+1) and sends its own ISN (seq = y). This proves the server is alive and ready, and allows bidirectional sequence tracking.

The client moves to ESTABLISHED state after this.

03
ACK — Acknowledge Client → Server

The client sends a final ACK (ack = y+1), confirming it received the server's ISN. Both sides are now in ESTABLISHED state and can send data. The entire handshake takes 1 RTT (Round Trip Time) before any data flows.

This is why TLS 1.3's 0-RTT and TCP Fast Open were invented — to reduce this overhead for repeat connections.

04
TCP Termination — FIN/ACK 4-Way

Closing uses a 4-step FIN/ACK exchange — because TCP is full-duplex, each direction closes independently. Either side sends FIN → other ACKs → other sends FIN → original ACKer sends final ACK. The initiator enters TIME_WAIT for 2×MSL to handle stray packets.

OkHttp avoids this cost by keeping connections alive in a pool.

TCP vs UDP
TCP — reliable, ordered, flow-controlled. Used by HTTP/1.1, HTTP/2, HTTPS. Higher overhead but guarantees delivery.

UDP — fire-and-forget. No handshake, no ordering. Used by DNS, video streaming, and HTTP/3 (QUIC). QUIC implements reliability at the application layer.
TCP Fast Open (TFO)
Allows data to be sent during the SYN phase for repeat connections using a cookie issued on first connect. Eliminates 1 full RTT of latency. Android supports TFO from API 21+, but OkHttp must be configured to use it explicitly.
Step 03

TLS / SSL Handshake

TLS (Transport Layer Security) builds an encrypted tunnel over TCP. It negotiates ciphers, authenticates the server via X.509 certificates, and derives shared session keys — all before a single byte of HTTP is sent.

TLS 1.2 handshake (2 RTT)
CLIENT
ClientHello
TLS version, ciphers, random
ServerHello
chosen cipher, server random
Certificate
X.509 cert chain
ServerHelloDone
ClientKeyExchange
RSA-encrypted premaster / ECDHE key
ChangeCipherSpec
Finished
ChangeCipherSpec + Finished
✓ 2 RTT — HTTPS ready
SERVER

TLS 1.2 requires 2 round trips after TCP to complete the handshake. Combined with TCP's 1 RTT, a fresh HTTPS connection costs 3 RTTs before the first HTTP byte. This is why connection reuse (keep-alive) and session resumption (session tickets / PSK) are critical optimizations.

TLS 1.3 handshake (1 RTT full, 0-RTT resume)
CLIENT
ClientHello + KeyShare
includes ECDHE public key + supported ciphers
ServerHello + KeyShare
Certificate + Verify + Finished
server derives keys immediately
Finished
✓ 1 RTT — 3× faster than TLS 1.2
SERVER
What changed in 1.3
• Removed RSA key exchange (no forward secrecy)
• Removed CBC cipher modes
• ECDHE is mandatory (forward secrecy always on)
• Merged messages to reduce RTT
• Session tickets → PSK (Pre-Shared Key)
0-RTT (Early Data)
On repeat connections, client can send application data in the first flight using a PSK from the previous session. This eliminates handshake latency entirely — but is vulnerable to replay attacks. Safe only for idempotent requests (GET, not POST).
X.509 Certificate Chain
Every TLS server presents a certificate chain:

Leaf (end-entity) cert — issued to yourdomain.com, contains public key

Intermediate CA — signed by the root, signs leaf certs (reduces root exposure)

Root CA — self-signed, pre-installed in device trust store (Android has ~130 trusted roots)
Certificate Fields
Subject — who owns this cert (CN, SAN)
Issuer — who signed it
Public Key — RSA-2048 or EC P-256
Validity — notBefore / notAfter
SAN — Subject Alternative Names (the actual domain list)
Signature — issuer's digital signature
SPKI — Subject Public Key Info (what we pin!)
Certificate Validation Steps
1. Chain builds to a trusted root
2. Each signature is valid
3. No cert is expired
4. Domain matches (CN or SAN)
5. No cert is revoked (CRL / OCSP)
6. Key usage flags permit TLS

All 6 must pass — failure = connection error
Certificate Types
DV (Domain Validation) — automated, proves domain control only. Let's Encrypt.

OV (Organization Validation) — org identity verified by CA

EV (Extended Validation) — rigorous vetting, used to show green bar (deprecated in browsers)
Cipher SuiteKey ExchangeAuthEncryptionMACForward Secrecy
TLS_AES_256_GCM_SHA384ECDHERSA/ECDSAAES-256-GCMSHA-384
TLS_CHACHA20_POLY1305_SHA256ECDHERSA/ECDSAChaCha20Poly1305
TLS_AES_128_GCM_SHA256ECDHERSA/ECDSAAES-128-GCMSHA-256
TLS_RSA_WITH_AES_256_CBC_SHARSARSAAES-256-CBCSHA-1✗ Removed in 1.3

Forward Secrecy means that even if the server's private key is compromised in the future, past recorded sessions cannot be decrypted — because the session key was ephemeral (ECDHE). Without FS (RSA key exchange), a stolen private key unlocks all past traffic. This is why TLS 1.3 mandates ECDHE.

Security

Attacks on the Connection

Every phase of the connection lifecycle has attack surfaces. Understanding these is essential to appreciating why TLS, certificate validation, and SSL pinning exist.

🕵️ Man-in-the-Middle (MITM)
An attacker positions themselves between client and server, intercepting and potentially modifying traffic. On plain HTTP this is trivial. On HTTPS it requires the attacker to present a fraudulent certificate that the client trusts — either by compromising a CA, installing a rogue root cert on the device, or exploiting validation bugs.
☠️ SSL Stripping
Attacker intercepts the initial HTTP request before the HTTPS redirect, acting as HTTPS proxy to the server but serving plain HTTP to the victim. The user never sees the lock icon. Mitigated by HSTS (HTTP Strict Transport Security) which forces browsers to always use HTTPS, and HSTS Preload lists baked into browsers.
🎭 DNS Spoofing / Cache Poisoning
Attacker injects malicious DNS responses, redirecting traffic for api.yourbank.com to a hostile IP. Even with a valid TLS cert for their own domain, the browser/app will detect the hostname mismatch. But if the attacker has a wildcard cert or a compromised CA, the attack can succeed without errors.
💉 Rogue Certificate Authority
Enterprises and governments can install their own root CAs on devices, allowing them to issue valid-looking certs for any domain. Android corporate MDM profiles commonly do this. Tools like Charles Proxy work exactly this way — they install a root cert so all app traffic can be inspected. Certificate pinning defeats this.
🔁 BEAST / POODLE / DROWN
Protocol-level cryptographic attacks on SSL 3.0 and TLS 1.0–1.1 exploiting weaknesses in CBC cipher padding. BEAST exploited CBC IV predictability. POODLE forced downgrade to SSL 3.0. DROWN exploited SSLv2 to attack TLS sessions. All mitigated by disabling old protocol versions.
🔓 Heartbleed (CVE-2014-0160)
A catastrophic bug in OpenSSL's heartbeat extension allowed attackers to read 64KB of server memory per request — leaking private keys, session tokens, and passwords. Affected ~17% of the internet. Demonstrates why implementation security matters as much as protocol design.
🔽 Downgrade Attacks
Attacker interferes with the ClientHello to make both sides negotiate a weaker cipher or older TLS version. TLS 1.3 defends against this by including a downgrade sentinel value in the server random field. OkHttp's ConnectionSpec lets you enforce minimum TLS versions and cipher suites.
🏓 SYN Flood (TCP DoS)
Attacker sends thousands of SYN packets with spoofed IPs, filling the server's SYN backlog queue with half-open connections. Legitimate connections are dropped. Mitigated by SYN cookies (server encodes state in the ISN) and rate limiting at the network layer.
Android

OkHttp Internals

OkHttp is Square's HTTP client for Android and Java. It's the networking backbone of Retrofit, Picasso, and countless Android apps. Here's why it's the gold standard.

5
Interceptor Layers
5
Max Idle Connections
5m
Keep-Alive Duration
H/2
HTTP/2 Native

OkHttp manages DNS, socket creation, TLS, HTTP/1.1 and HTTP/2 multiplexing, connection pooling, retries, redirects, and gzip decompression — all transparently, through a layered interceptor chain.

The Interceptor Chain

Every request passes through a chain of interceptors — in order on the way out, reverse order on the way back. User interceptors fire first, network interceptors fire last.

Your App Interceptor
Add auth headers, log requests, modify URLs, A/B routing
↓ request proceeds
RetryAndFollowUpInterceptor
Handles 301/302 redirects, auth challenges (401/407), connection failures
↓ request proceeds
BridgeInterceptor
Adds Content-Type, Content-Length, Accept-Encoding: gzip, Cookie headers
↓ request proceeds
CacheInterceptor
Checks DiskLruCache, honors Cache-Control headers, serves stale responses offline
↓ hits network only if needed
Your Network Interceptor
Log actual wire data, see compressed bytes, inspect redirected requests
↓ proceeds to actual network
ConnectInterceptor
Leases a connection from the pool or creates new: DNS → TCP → TLS
↓ connection established
CallServerInterceptor
Writes request to socket, reads response headers + body via HTTP/1.1 or HTTP/2
// Building an OkHttp client with interceptors val client = OkHttpClient.Builder() .addInterceptor { chain -> // App interceptor — always runs, even for cached responses val request = chain.request().newBuilder() .addHeader("Authorization", "Bearer $token") .build() chain.proceed(request) } .addNetworkInterceptor { chain -> // Network interceptor — sees actual wire request val response = chain.proceed(chain.request()) response.newBuilder() .header("Cache-Control", "max-age=3600") .build() } .build()
Security

SSL Pinning in OkHttp

Certificate pinning hardcodes the expected public key fingerprint of your server's certificate. Even if a rogue CA issues a valid cert for your domain, the pinned client will reject it.

✅ Legitimate Server
Expected pin: sha256/AAAA...1234
Received pin: sha256/AAAA...1234

✓ MATCH → connection allowed
App proceeds normally. The server's SPKI hash matches the hardcoded value.
❌ MITM / Rogue Cert
Expected pin: sha256/AAAA...1234
Received pin: sha256/EVIL...9999

✗ MISMATCH → SSLPeerUnverifiedException
Even a CA-signed cert for your domain is rejected if the SPKI doesn't match.
// SSL Pinning with OkHttp's CertificatePinner val certificatePinner = CertificatePinner.Builder() // Pin the SPKI hash of your leaf certificate .add("api.example.com", "sha256/AAAA1234...") // Always pin a backup key to handle rotation .add("api.example.com", "sha256/BBBB5678...") // Wildcard support .add("**.example.com", "sha256/ROOT_PIN...") .build() val client = OkHttpClient.Builder() .certificatePinner(certificatePinner) .build() // Get your pin (run this once against your server) // CertificatePinner.pin(certificate) returns "sha256/..."
What We Pin
OkHttp pins the SPKI fingerprint (Subject Public Key Info hash) — the public key portion only, not the full cert. This means a cert can be renewed/re-issued by the CA and the pin still works as long as the key pair is preserved. Alternatively, you can pin the intermediate CA's key for longer validity.
Backup Pins are Mandatory
Always configure at least 2 pins. If you rotate your key and only have one pin, your app will break for all users until they update. Pin your current key and your next rotation key (generated but not yet deployed). OkHttp passes if any configured pin matches.
Bypassing Pinning
Attackers use Frida or Xposed frameworks to hook OkHttp internals and disable pinning at runtime. Root detection and integrity checks are separate defenses. OkHttp 5.x adds stricter enforcement, making hooking harder.
Q
Why not just validate the CA normally?

Normal CA validation trusts any of ~130 root CAs pre-installed on Android. A government or attacker who can compromise one of those CAs — or install their own root (e.g. via MDM) — can issue a valid cert for your domain. Pinning ignores the system trust store and checks only your specific key. This is why banking apps, VPNs, and healthcare apps use it.

Q
When should you NOT pin?

Don't pin if you don't control your certificate rotation process, or if you integrate third-party APIs (you can't predict their cert rotation). Don't pin for CDN-hosted assets where the cert changes per PoP. The risk of a broken app from a missed rotation often outweighs the benefit for low-sensitivity apps. Use network security config on Android for a declarative alternative.

Performance

Why OkHttp is Efficient

OkHttp doesn't just implement HTTP — it aggressively optimizes every phase of the connection lifecycle to minimize latency and battery usage on mobile.

Connection Pool — Interactive Demo
01
Connection Pooling Core Feature

OkHttp maintains a ConnectionPool of idle HTTP/1.1 sockets and HTTP/2 connections. When you make a request, OkHttp checks if a pooled connection exists for that host:port pair. If yes — no DNS lookup, no TCP handshake, no TLS handshake. Just write the request.

Default: 5 connections max, 5 minutes keep-alive. HTTP/2 connections multiplex many requests over one socket, so often only 1 pooled connection per host is needed. Connections are evicted by a background cleanup task using GC root detection (no GC tricks — just reference counting).

02
HTTP/2 Multiplexing Massive Win

HTTP/1.1 sends requests serially per connection (or uses multiple connections which wastes resources). HTTP/2 uses a single TCP connection and multiplexes many streams concurrently — each request is a stream with a unique ID. Server can push resources before the client asks. Headers are compressed with HPACK.

Effect: a single OkHttp connection to an HTTP/2 server can handle 100+ concurrent requests, vs 6 parallel connections for HTTP/1.1. This cuts mobile radio wake-up time (huge for battery life) and eliminates head-of-line blocking at the HTTP layer.

03
Transparent Gzip Compression Automatic

BridgeInterceptor automatically adds Accept-Encoding: gzip to outgoing requests. When the server responds with gzip-encoded body, OkHttp decompresses it transparently before returning to your code — you always see raw bytes. This typically reduces JSON response sizes by 60–80%, slashing bandwidth and parse time.

04
Intelligent Retry Logic Reliability

RetryAndFollowUpInterceptor handles connection failures, stale pooled connections (automatically retried with a fresh connection), 307/308 redirects, 401/407 authentication challenges, and 503 with Retry-After header. It enforces a maximum of 20 follow-ups to prevent infinite redirect loops.

For idempotent requests (GET, HEAD), OkHttp can retry on network failures. POST is not retried by default (not safe to duplicate).

05
Disk Cache (DiskLruCache) Offline First

OkHttp's CacheInterceptor implements RFC 7234 HTTP caching. Responses with Cache-Control, Expires, or ETag headers are stored in a size-bounded LRU disk cache. Cache-Control: max-age serves without network. ETags enable conditional requests (304 Not Modified) which skip transferring the body. You can force FORCE_CACHE for offline mode.

06
Happy Eyeballs — Dual-Stack IPv4/IPv6 RFC 6555

OkHttp implements Happy Eyeballs — when a hostname has both IPv4 and IPv6 addresses, it attempts connections in parallel (with a 250ms stagger for IPv6 preference) and uses whichever completes first. This ensures that broken IPv6 deployments don't add latency — the IPv4 fallback wins quickly. Users on IPv6 networks see reduced latency.

07
Single OkHttpClient Instance — Critical Pattern Best Practice

OkHttpClient holds the connection pool, thread pool, cache, and DNS resolver. Creating a new OkHttpClient per request destroys all pooling benefits and leaks threads. Use a single application-scoped instance (singleton, Dagger/Hilt, or Application class). Use newBuilder() to create lightweight variants that share the pool.

OkHttp vs Alternatives

FeatureOkHttpHttpURLConnectionVolleyKtor
HTTP/2✓ Native
Connection Pool✓ Built-inLimited
TLS ConfigFull controlBasicLimitedFull control
Certificate Pinning✓ Built-inManualManualPlugin
Interceptors✓ PowerfulLimited
Gzip autoManual
Retrofit compat✓ NativeNo
Used byRetrofit, Picasso, Coil, FirebaseLegacy AndroidGoogle appsJetBrains

Production OkHttp Setup

// Singleton — share this across your entire app object NetworkClient { private val certificatePinner = CertificatePinner.Builder() .add("api.example.com", "sha256/current_pin...") .add("api.example.com", "sha256/backup_pin...") .build() private val loggingInterceptor = HttpLoggingInterceptor().apply { level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE } val client = OkHttpClient.Builder() // Security .certificatePinner(certificatePinner) .connectionSpecs(listOf( ConnectionSpec.MODERN_TLS // TLS 1.2+ only, strong ciphers )) // Performance .connectionPool(ConnectionPool( maxIdleConnections = 10, keepAliveDuration = 10, TimeUnit.MINUTES )) .protocols(listOf(Protocol.HTTP_2, Protocol.HTTP_1_1)) // Reliability .connectTimeout(15, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .retryOnConnectionFailure(true) // Observability .addInterceptor(loggingInterceptor) .addNetworkInterceptor(StethoInterceptor()) // Chrome DevTools // Cache .cache(Cache( directory = File(context.cacheDir, "http_cache"), maxSize = 50L * 1024 * 1024 // 50 MB )) .build() }